﻿using Fast.Extensions;
using Fast.Reflection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Fast.RemoteRequest
{
    /// <summary>
    /// 远程调用代理类
    /// </summary>
    [SkipScan]
    public class HttpDynamicProxy : AspectDispatchProxy, IDispatchProxy
    {
        /// <summary>
        /// 实例对象
        /// </summary>
        public object Decorated { get; set; }
        
        /// <summary>
        /// 同步拦截，暂不支持
        /// </summary>
        /// <param name="targetMethod"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        /// <exception cref="NotSupportedException"></exception>
        protected override object Invoke(MethodInfo targetMethod, object[] args)
        {
            throw new NotSupportedException("请使用异步操作模式。");
        }

        /// <summary>
        /// 异步拦截无返回值的方法
        /// </summary>
        /// <param name="targetMethod"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        protected override async Task InvokeAsync(MethodInfo targetMethod, object[] args)
        {
            var (httpMethod, httpRemote) = BuildHttpRemoteRequest(targetMethod, args);
            await httpRemote.SendAsync(httpMethod);
        }

        /// <summary>
        /// 异步拦截有返回值的方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="targetMethod"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        protected override async Task<T> InvokeWithResultAsync<T>(MethodInfo targetMethod, object[] args)
        {
            var (httpMethod, httpRemote) = BuildHttpRemoteRequest(targetMethod, args);
            return await httpRemote.SendAsync<T>(httpMethod);
        }

        /// <summary>
        /// 构建远程请求类
        /// </summary>
        /// <param name="targetMethod"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        private (HttpMethod, IHttpRemoteRequest) BuildHttpRemoteRequest(MethodInfo targetMethod, object[] args)
        {
            // 判断方法是否是远程代理请求方法
            if (!targetMethod.IsDefined(typeof(HttpMethodBaseAttribute), true))
                throw new InvalidOperationException($"{targetMethod.Name} 不是有效的请求代理方法。");

            // 解析方法参数及参数值
            var parameters = targetMethod.GetParameters().Select((u, i) => new MethodParameterInfo
            {
                Parameter = u,
                Name = u.Name,
                Value = args[i]
            });

            // 获取请求配置
            var httpRemoteRequestAttribute = targetMethod.GetCustomAttribute<HttpMethodBaseAttribute>(true);

            var httpRemoteRequest = App.GetRequiredService<IHttpRemoteRequest>();
            httpRemoteRequest.SetRequestUrl(httpRemoteRequestAttribute.Url);

            // 设置请求客户端
            var clientAttribute = targetMethod.GetAttribute<ClientAttribute>(true);
            if (clientAttribute != null) httpRemoteRequest.SetClientName(clientAttribute.Name);

            // 设置请求超时时间
            var timeout = targetMethod.GetAttribute<TimeoutAttribute>(true)?.Seconds;
            if (timeout != null && timeout.Value > 0) httpRemoteRequest.SetClientTimeout(timeout.Value);

            // 设置请求报文头
            SetHeaders(targetMethod, parameters, httpRemoteRequest);

            // 设置 Url 地址参数
            SetQueryParams(parameters, httpRemoteRequest);

            // 设置 Body 信息
            SetBody(parameters, httpRemoteRequest);

            return (httpRemoteRequestAttribute.HttpMethod, httpRemoteRequest);
        }

        /// <summary>
        /// 设置请求报文头
        /// </summary>
        /// <param name="targetMethod"></param>
        /// <param name="parameters"></param>
        /// <param name="httpRemoteRequest"></param>
        private static void SetHeaders(MethodInfo targetMethod, IEnumerable<MethodParameterInfo> parameters, IHttpRemoteRequest httpRemoteRequest)
        {
            var declaringType = targetMethod.DeclaringType;

            // 获取声明类请求报文头
            var declaringTypeHeaders = (declaringType.IsDefined(typeof(HeadersAttribute), true)
                ? declaringType.GetCustomAttributes<HeadersAttribute>(true)
                : Array.Empty<HeadersAttribute>()).ToDictionary(u => u.Key, u => u.Value);

            // 获取方法请求报文头
            var methodHeaders = (targetMethod.IsDefined(typeof(HeadersAttribute), true)
                ? targetMethod.GetCustomAttributes<HeadersAttribute>(true)
                : Array.Empty<HeadersAttribute>()).ToDictionary(u => u.Key, u => u.Value);

            // 获取参数请求报文头
            var headerParameters = parameters.Where(u => u.Parameter.IsDefined(typeof(HeadersAttribute), true));
            var parameterHeaders = new Dictionary<string, object>();
            foreach (var item in headerParameters)
            {
                var headersAttribute = item.Parameter.GetCustomAttribute<HeadersAttribute>(true);
                if (item.Value != null)
                {
                    // 处理参数传入 [Headers] IDictionary<string, object>  情况
                    if (item.Value is IDictionary<string, object> dicHeaders)
                    {
                        foreach (var (key, value) in dicHeaders)
                        {
                            parameterHeaders.Add(key, value);
                        }
                    }
                    else
                    {
                        parameterHeaders.Add(headersAttribute.Key ?? item.Name, item.Value);
                    }
                }
            }

            // 合并所有请求报文头
            var headers = declaringTypeHeaders.AddOrUpdate(methodHeaders).AddOrUpdate(parameterHeaders);
            httpRemoteRequest.SetHeaders(headers);
        }

        /// <summary>
        /// 设置 Url 地址参数
        /// </summary>
        /// <param name="parameters"></param>
        /// <param name="httpRemoteRequest"></param>
        private static void SetQueryParams(IEnumerable<MethodParameterInfo> parameters, IHttpRemoteRequest httpRemoteRequest)
        {
            // 配置 Url 地址参数
            var queryParameters = parameters.Where(u => u.Parameter.IsDefined(typeof(QueryStringAttribute), true));
            var parameterQueries = new Dictionary<string, object>();
            foreach (var item in queryParameters)
            {
                var queryStringAttribute = item.Parameter.GetCustomAttribute<QueryStringAttribute>();
                if (item.Value != null) parameterQueries.Add(queryStringAttribute.Alias ?? item.Name, item.Value);
            }
            httpRemoteRequest.SetQueryParams(parameterQueries);
        }

        /// <summary>
        /// 设置 Body 参数
        /// </summary>
        /// <param name="parameters"></param>
        /// <param name="httpRemoteRequest"></param>
        private static void SetBody(IEnumerable<MethodParameterInfo> parameters, IHttpRemoteRequest httpRemoteRequest)
        {
            // 配置 Body 参数，只取第一个
            var bodyParameter = parameters.FirstOrDefault(u => u.Parameter.IsDefined(typeof(BodyAttribute), true));
            if (bodyParameter != null)
            {
                var bodyAttribute = bodyParameter.Parameter.GetCustomAttribute<BodyAttribute>(true);
                httpRemoteRequest.SetBody(bodyParameter.Value, bodyAttribute.ContentType, Encoding.GetEncoding(bodyAttribute.Encoding));
            }

            // 查找所有贴了 [BodyBytes] 特性的参数
            var bodyBytesParameters = parameters.Where(u => u.Parameter.IsDefined(typeof(BodyBytesAttribute), true));
            if (bodyBytesParameters != null)
            {
                var bodyBytes = new List<(string Name, byte[] Bytes, string FileName)>();
                foreach (var item in bodyBytesParameters)
                {
                    var bodyBytesAttribute = item.Parameter.GetCustomAttribute<BodyBytesAttribute>();
                    if (item.Value != null && item.Value.GetType() == typeof(byte[]))
                        bodyBytes.Add((bodyBytesAttribute.Alias ?? item.Name, (byte[])item.Value, bodyBytesAttribute.FileName));
                }

                httpRemoteRequest.SetBodyBytes(bodyBytes.ToArray());
            }
        }
    }
}
